原始题目:剑指 Offer 11. 旋转数组的最小数字 (opens new window)

# 方法一:二分查找

题目给的数组是有序数组,可以考虑使用二分查找,可以将遍历法的线性级别转化为对数级别

假定数组的左边位置是 leftleft ,右边是 rightrightleftleftrightright 的中心位置为 mid,mid=(left+mid)/2mid, mid = (left + mid) / 2

  • nums[mid]<nums[right]nums[mid] < nums[right] , 说明 [mid,right][mid, right] 区间是递增的,那么最小值应该出现在 [left,mid][left, mid] 区间。

  • nums[mid]>nums[right]nums[mid] > nums[right], 说明 [mid,right][mid, right] 区间是先递增后递减的,那么最小值应该现在 (mid+1,right](mid + 1, right] 区间。nums[mid]nums[mid] 不会是最小值。

  • nums[mid]==nums[right]nums[mid] == nums[right], 则无法判断 [mid,right][mid, right] 区间的单调性,但是可以把 nums[right]nums[right] 去掉。因为即使去掉 nums[right]nums[right] ,数组中还有 nums[mid]nums[mid] ,所以不会对结果产生影响,把区间缩减为 [left,right)[left, right)

补充说明:

上面的思路中,一直都是 midmidrightright 的对比,那么能不能将 leftleftmidmid 对比呢?

举例:[3,4,5,1,2][3, 4, 5, 1, 2][1,2,3,4,5][1, 2, 3, 4, 5] ,此时,中间位置的值都比左边大,但最小值一个在后面,一个在前面,因此这种做法不能有效地减治。

代码:

public int minArray(int[] nums) {
    if (nums == null || nums.length == 0) {
        return -1;
    }
    int l = 0;
    int r = nums.length - 1;
    while (l < r) {
        int mid = l + ((r - l) >> 1);
        if(nums[mid] < nums[r]){
            r = mid;
        } else if (nums[mid] > nums[r]){
            l = mid+1;
        } else {
            // 舍弃 nums[r]
            r--;
        }
    }
    return nums[r];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

复杂度分析

  • 时间复杂度O(logN)O(logN): 在特例情况下(例如 [1,1,1,1][1, 1, 1, 1]),会退化到 O(N)O(N)

  • **空间复杂度O(1)O(1) **:l,r,midl, r, mid 使用的都是常数大小的空间。

上次更新: 2023/10/15